package com.chlp.core.framework.filter;

import org.apache.commons.io.output.TeeOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

/**
 * @author ChenLiPeng
 */
public class HttpLoggingFilter implements Filter {

	private final Logger log = LogManager.getLogger(HttpLoggingFilter.class);

	@Override
	public void init(FilterConfig filterConfig) {}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
		try {
			HttpServletRequest httpServletRequest = (HttpServletRequest) request;
			HttpServletResponse httpServletResponse = (HttpServletResponse) response;

			Map<String, String> requestMap = this.getTypesafeRequestMap(httpServletRequest);
			BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(httpServletRequest);
			BufferedResponseWrapper bufferedResponse = new BufferedResponseWrapper(httpServletResponse);
			String basePath = request.getScheme() + "://" + httpServletRequest.getHeader("X-Forwarded-Host");
			String path = basePath + httpServletRequest.getRequestURI();

			// 接口请求参数信息
			final StringBuilder logInfo = new StringBuilder()
					.append("REST_Request_Info").append("\n")
					.append("{").append("\n")
					.append("   \"Method\":").append("\"").append(httpServletRequest.getMethod()).append("\"").append(",\n")
					.append("   \"Path\":").append("\"").append(path).append("\"").append(",\n")
					.append("   \"Parameters\":").append(requestMap).append(",\n")
					.append("   \"Body\":").append(bufferedRequest.getRequestBody()).append(",\n")
					.append("   \"Address\":").append("\"").append(httpServletRequest.getRemoteAddr()).append("\"").append(",\n");

			chain.doFilter(bufferedRequest, bufferedResponse);

			if (!path.contains(".html") && !path.contains(".js") && !path.contains(".css") && !path.contains(".png")
					&& !path.contains(".jpeg") && !path.contains(".jpg") && !path.contains(".tff") && !path.contains(".woff2")) {
				// 接口响应参数信息
				logInfo.append("   \"Response\":").append(bufferedResponse.getContent()).append("\n")
					   .append("}").append("\n");
			}
		} catch (Throwable a) {
			a.getStackTrace();
			log.error(a.getMessage());
		}
	}

	private Map<String, String> getTypesafeRequestMap(HttpServletRequest request) {
		Map<String, String> typesafeRequestMap = new HashMap<>(16);
		Enumeration<?> requestParamNames = request.getParameterNames();
		while (requestParamNames.hasMoreElements()) {
			String requestParamName = (String) requestParamNames.nextElement();
			String requestParamValue;
			if ("password".equalsIgnoreCase(requestParamName)) {
				requestParamValue = "********";
			} else {
				requestParamValue = request.getParameter(requestParamName);
			}
			typesafeRequestMap.put(requestParamName, requestParamValue);
		}
		return typesafeRequestMap;
	}

	@Override
	public void destroy() {
	}

	private static final class BufferedRequestWrapper extends HttpServletRequestWrapper {

		private final byte[] buffer;

		BufferedRequestWrapper(HttpServletRequest req) throws IOException {
			super(req);
			// Read InputStream and store its content in a buffer.
			InputStream is = req.getInputStream();
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			byte[] buf = new byte[1024];
			int read;
			while ((read = is.read(buf)) > 0) {
				baos.write(buf, 0, read);
			}
			this.buffer = baos.toByteArray();
		}

		@Override
		public ServletInputStream getInputStream() {
            return new BufferedServletInputStream(new ByteArrayInputStream(this.buffer));
		}

		String getRequestBody() throws IOException {
			BufferedReader reader = new BufferedReader(new InputStreamReader(this.getInputStream()));
			String line;
			StringBuilder inputBuffer = new StringBuilder();
			do {
				line = reader.readLine();
				if (null != line) {
					inputBuffer.append(line.trim());
				}
			} while (line != null);
			reader.close();
			return inputBuffer.toString().trim();
		}

	}

	private static final class BufferedServletInputStream extends ServletInputStream {

		private final ByteArrayInputStream bais;

		BufferedServletInputStream(ByteArrayInputStream bais) {
			this.bais = bais;
		}

		@Override
		public int available() {
			return this.bais.available();
		}

		@Override
		public int read() {
			return this.bais.read();
		}

		@Override
		public int read(byte[] buf, int off, int len) {
			return this.bais.read(buf, off, len);
		}

		@Override
		@SuppressWarnings("unused")
		public boolean isFinished() {
			return false;
		}

		@Override
		@SuppressWarnings("unused")
		public boolean isReady() {
			return true;
		}

		@Override
		@SuppressWarnings("unused")
		public void setReadListener(ReadListener readListener) {

		}
	}

	public static class TeeServletOutputStream extends ServletOutputStream {

		private final TeeOutputStream targetStream;

		TeeServletOutputStream(OutputStream one, OutputStream two) {
			targetStream = new TeeOutputStream(one, two);
		}

		@Override
		public void write(int arg0) throws IOException {
			this.targetStream.write(arg0);
		}

		@Override
		public void flush() throws IOException {
			super.flush();
			this.targetStream.flush();
		}

		@Override
		public void close() throws IOException {
			super.close();
			this.targetStream.close();
		}

		@Override
		public boolean isReady() {
			return false;
		}

		@Override
		public void setWriteListener(WriteListener writeListener) {

		}
	}

	public static class BufferedResponseWrapper implements HttpServletResponse {

		HttpServletResponse original;
		TeeServletOutputStream tee;
		ByteArrayOutputStream bos;

		BufferedResponseWrapper(HttpServletResponse response) {
			original = response;
		}

		String getContent() {
			return bos.toString();
		}

		@Override
		public PrintWriter getWriter() throws IOException {
			return original.getWriter();
		}

		@Override
		public ServletOutputStream getOutputStream() throws IOException {
			if (tee == null) {
				bos = new ByteArrayOutputStream();
				tee = new TeeServletOutputStream(original.getOutputStream(), bos);
			}
			return tee;

		}

		@Override
		public String getCharacterEncoding() {
			return original.getCharacterEncoding();
		}

		@Override
		public String getContentType() {
			return original.getContentType();
		}

		@Override
		public void setCharacterEncoding(String charset) {
			original.setCharacterEncoding(charset);
		}

		@Override
		public void setContentLength(int len) {
			original.setContentLength(len);
		}

		@Override
		public void setContentLengthLong(long l) {
			original.setContentLengthLong(l);
		}

		@Override
		public void setContentType(String type) {
			original.setContentType(type);
		}

		@Override
		public void setBufferSize(int size) {
			original.setBufferSize(size);
		}

		@Override
		public int getBufferSize() {
			return original.getBufferSize();
		}

		@Override
		public void flushBuffer() throws IOException {
			tee.flush();
		}

		@Override
		public void resetBuffer() {
			original.resetBuffer();
		}

		@Override
		public boolean isCommitted() {
			return original.isCommitted();
		}

		@Override
		public void reset() {
			original.reset();
		}

		@Override
		public void setLocale(Locale loc) {
			original.setLocale(loc);
		}

		@Override
		public Locale getLocale() {
			return original.getLocale();
		}

		@Override
		public void addCookie(Cookie cookie) {
			original.addCookie(cookie);
		}

		@Override
		public boolean containsHeader(String name) {
			return original.containsHeader(name);
		}

		@Override
		public String encodeURL(String url) {
			return original.encodeURL(url);
		}

		@Override
		public String encodeRedirectURL(String url) {
			return original.encodeRedirectURL(url);
		}

		@SuppressWarnings("deprecation")
		@Override
		public String encodeUrl(String url) {
			return original.encodeUrl(url);
		}

		@SuppressWarnings("deprecation")
		@Override
		public String encodeRedirectUrl(String url) {
			return original.encodeRedirectUrl(url);
		}

		@Override
		public void sendError(int sc, String msg) throws IOException {
			original.sendError(sc, msg);
		}

		@Override
		public void sendError(int sc) throws IOException {
			original.sendError(sc);
		}

		@Override
		public void sendRedirect(String location) throws IOException {
			original.sendRedirect(location);
		}

		@Override
		public void setDateHeader(String name, long date) {
			original.setDateHeader(name, date);
		}

		@Override
		public void addDateHeader(String name, long date) {
			original.addDateHeader(name, date);
		}

		@Override
		public void setHeader(String name, String value) {
			original.setHeader(name, value);
		}

		@Override
		public void addHeader(String name, String value) {
			original.addHeader(name, value);
		}

		@Override
		public void setIntHeader(String name, int value) {
			original.setIntHeader(name, value);
		}

		@Override
		public void addIntHeader(String name, int value) {
			original.addIntHeader(name, value);
		}

		@Override
		public void setStatus(int sc) {
			original.setStatus(sc);
		}

		@SuppressWarnings("deprecation")
		@Override
		public void setStatus(int sc, String sm) {
			original.setStatus(sc, sm);
		}

		@Override
		public String getHeader(String arg0) {
			return original.getHeader(arg0);
		}

		@Override
		public Collection<String> getHeaderNames() {
			return original.getHeaderNames();
		}

		@Override
		public Collection<String> getHeaders(String arg0) {
			return original.getHeaders(arg0);
		}

		@Override
		public int getStatus() {
			return original.getStatus();
		}

	}
}